﻿// 
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// 

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

#if NETFX_CORE
using System.Linq;
#endif

namespace NetworkCommsDotNet.DPSBase
{
    /// <summary>
    /// Provides methods that process data in a <see cref="System.IO.Stream"/> into another <see cref="System.IO.Stream"/>.  Can be used to provide features such as data compression or encryption
    /// </summary>
    /// <example>
    /// <code source="..\NetworkCommsDotNet\DPSBase\RijndaelPSKEncrypter.cs" lang="cs" title="Implementation Example" />
    /// </example>
    public abstract class DataProcessor
    {
        private static Dictionary<Type, byte> cachedIdentifiers = new Dictionary<Type, byte>();
        private static Dictionary<Type, bool> cachedIsSecurity = new Dictionary<Type, bool>();
        private static object locker = new object();

        /// <summary>
        /// Helper function to allow a <see cref="DataProcessor"/> to be implemented as a singleton.  Returns the singleton instance generated by the <see cref="DPSManager"/>
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> of the <see cref="DataProcessor"/> to retrieve from the <see cref="DPSManager"/></typeparam>
        /// <returns>The singleton instance generated by the <see cref="DPSManager"/></returns>
        [Obsolete("Instances of singleton DataProcessors should be accessed via the DPSManager")]
        protected static T GetInstance<T>() where T : DataProcessor
        {
            //this forces helper static constructor to be called and gets us an instance if composition worked
            var instance = DPSManager.GetDataProcessor<T>() as T;

            if (instance == null)
            {
                //if the instance is null the type was not added as part of composition
                //create a new instance of T and add it to helper as a compressor
#if NETFX_CORE
                var construct = (from constructor in typeof(T).GetTypeInfo().DeclaredConstructors
                                 where constructor.GetParameters().Length == 0
                                 select constructor).FirstOrDefault();
#else
                var construct = typeof(T).GetConstructor(new Type[] { });
                if (construct == null)
                    construct = typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
#endif
                if (construct == null)
                    throw new Exception();

                instance = construct.Invoke(new object[] { }) as T;
                
                DPSManager.AddDataProcessor(instance);
            }

            return instance;
        }

        /// <summary>
        /// Returns a unique identifier for the compressor type. Used in automatic serialization/compression detection
        /// </summary>
        public byte Identifier
        {
            get
            {
                lock (locker)
                {
                    Type typeOfThis = this.GetType();

                    if (!cachedIdentifiers.ContainsKey(typeOfThis))
                    {
#if NETFX_CORE
                        var attributes = this.GetType().GetTypeInfo().GetCustomAttributes(typeof(DataSerializerProcessorAttribute), false).ToArray();
#else
                        var attributes = this.GetType().GetCustomAttributes(typeof(DataSerializerProcessorAttribute), false);
#endif
                        if (attributes.Length == 1)
                            cachedIdentifiers[typeOfThis] = (attributes[0] as DataSerializerProcessorAttribute).Identifier;
                        else
                            throw new Exception("Data serializer and processor types must have a DataSerializerProcessorAttribute specifying a unique id");
                    }

                    return cachedIdentifiers[typeOfThis];
                }
            }
        }

        /// <summary>
        /// Returns a boolian stating whether this data processor is security critical
        /// </summary>
        public bool IsSecurityCritical
        {
            get
            {
                lock (locker)
                {
                    Type typeOfThis = this.GetType();

                    if (!cachedIsSecurity.ContainsKey(typeOfThis))
                    {
#if NETFX_CORE
                        var attributes = this.GetType().GetTypeInfo().GetCustomAttributes(typeof(SecurityCriticalDataProcessorAttribute), false).ToArray();
#else
                        var attributes = this.GetType().GetCustomAttributes(typeof(SecurityCriticalDataProcessorAttribute), false);
#endif
                        if (attributes != null && attributes.Length > 0)
                            cachedIsSecurity[typeOfThis] = (attributes[0] as SecurityCriticalDataProcessorAttribute).IsSecurityCritical;
                        else
                            cachedIsSecurity[typeOfThis] = false;
                    }

                    return cachedIsSecurity[typeOfThis];
                }
            }
        }

        /// <summary>
        /// Processes data held in a stream and outputs it to another stream
        /// </summary>
        /// <param name="inStream">An input stream containing data to be processed</param>
        /// <param name="outStream">An output stream to which the processed data is written</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        /// <param name="writtenBytes">The size of the data written to the output stream</param>        
        public abstract void ForwardProcessDataStream(Stream inStream, Stream outStream, Dictionary<string, string> options, out long writtenBytes);

        /// <summary>
        /// Processes data, in reverse, that is held in a stream and outputs it to another stream
        /// </summary>
        /// <param name="inStream">An input stream containing data to be processed</param>
        /// <param name="outStream">An output stream to which the processed data is written</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        /// <param name="writtenBytes">The size of the data written to the output stream</param>                
        public abstract void ReverseProcessDataStream(Stream inStream, Stream outStream, Dictionary<string, string> options, out long writtenBytes);
    }
}
